/*
 * Copyright (c) 2016-2021, Hangzhou Qulian Technology Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "largefile.h"

// ----------------------------------------------
// large file type definition
// ----------------------------------------------
typedef OhByteType  FileByteType;
typedef OhSizeType  FileSizeType;
typedef OhErrType   FileErrType;
typedef OhVoidType  FileVoidType;

// ----------------------------------------------
// large file Function
// ----------------------------------------------
// -- Create file object. --
FileErrType FileInfoNew(LargefileType* f, FileByteType *path, FileByteType *name, FileByteType *des, FileByteType *node, FileByteType *user) {

    if(f == NULL || path == NULL || name == NULL || des == NULL || node == NULL || user == NULL) {
        return FILE_ERR_PARAM_INVAILD;
    }
    f->size = 0;
    // path
    f->path = calloc(strlen((char*)path)+1, sizeof(FileByteType));
    if (f->path == NULL) {
        return FILE_ERR_CALLOC_MEMORY;
    }
    memcpy(f->path, path, strlen((char*)path));
    // name
    f->name = calloc(strlen((char*)name)+1, sizeof(FileByteType));
    if (f->name == NULL) {
        return FILE_ERR_CALLOC_MEMORY;
    }
    memcpy(f->name, name, strlen((char*)name));
    // node
    f->node = calloc(strlen((char*)node)+1, sizeof(FileByteType));
    if (f->node == NULL) {
        return FILE_ERR_CALLOC_MEMORY;
    }
    memcpy(f->node, node, strlen((char*)node));
    // user
    f->user = calloc(strlen((char*)user)+1, sizeof(FileByteType));
    if (f->user == NULL) {
        return FILE_ERR_CALLOC_MEMORY;
    }
    memcpy(f->user, user, strlen((char*)user));
    // des
    f->des = calloc(strlen((char*)des)+1, sizeof(FileByteType));
    if (f->des == NULL) {
        return FILE_ERR_CALLOC_MEMORY;
    }
    memcpy(f->des, des, strlen((char*)des));
    log_debug("%s",node);
    return FILE_SUCCESS;
}

// -- Calculate file MD5 result. --
FileErrType FileCalMd5(LargefileType* f) {

    // open file
    FILE *fp = fopen((char*)f->path, "rb");
    if(fp == NULL) {
        log_error("File not exists : %s", f->path);
        return FILE_ERR_NOT_EXIST;
    }
    // function param
    FileSizeType blockSize = 32 * 1024;
    FileByteType* buf = calloc(blockSize, sizeof(FileByteType));
    if (buf == NULL) {
        fclose(fp);
        return FILE_ERR_CALLOC_MEMORY;
    }
    FileByteType blockMd5Result[16];
    MD5_CTX fileMd5;
    MD5Init(&fileMd5);
    // calculate file md5
    while(1){
        // read file
        FileSizeType realSize = fread(buf, 1, blockSize, fp);
        f->size += realSize;
        if(realSize > 0) {
            // calculate block md5
            MD5_CTX blockMd5;
            MD5Init(&blockMd5);
            MD5Update(&blockMd5, buf, realSize);
            MD5Final(&blockMd5, blockMd5Result);
            // calculate file md5
            MD5Update(&fileMd5, blockMd5Result, 16);
        }
        if(feof(fp)) break;
    }
    MD5Final(&fileMd5, blockMd5Result);
    // format result hex to string
    FileSizeType i;
    for(i = 0; i < 16; i ++){
        f->hash[i*2  ] = ((blockMd5Result[i] & 0xf0) >> 4) <=9 ?  ((blockMd5Result[i] & 0xf0) >> 4) + '0' : ((blockMd5Result[i] & 0xf0) >> 4) + 'a' - 10;
        f->hash[i*2+1] = ((blockMd5Result[i] & 0x0f)     ) <=9 ?  ((blockMd5Result[i] & 0x0f)     ) + '0' : ((blockMd5Result[i] & 0x0f)     ) + 'a' - 10;
    }
    f->hash[32] = '\0';
    log_debug("file md5 is : %s", f->hash);
    log_debug("file size is : %d", f->size);
    // clear cache
    fclose(fp);
    free(buf);

    return FILE_SUCCESS;
}

// -- Format file information into extra string format. --
FileErrType FileInfoFormat(LargefileType* f, FileByteType* buf, FileSizeType bufLen) {

    FileByteType ex[] = "{\"hash\":\"%s\",\"file_name\":\"%s\",\"file_size\":%d,\"node_list\":[%s],\"user_list\":[%s],\"file_description\":\"%s\"}";
    FileSizeType formatSize = strlen((char*)ex);
    FileSizeType infoSize = strlen((char*)f->hash) + strlen((char*)f->name) + sizeof(f->size)*32 +
                            strlen((char*)f->node) + strlen((char*)f->user) + strlen((char*)f->des);
    if(formatSize + infoSize + 1 > bufLen) {
        log_error("The planned space size needs to be greater than %d, but the actual space size is %d", formatSize + infoSize + 1, bufLen);
        return FILE_ERR_BUF_TOO_SMALL;
    }
    sprintf((char*)buf, (char*)ex, f->hash, f->name, f->size, f->node, f->user, f->des);
    log_info("%s", buf);
    return FILE_SUCCESS;
}

// -- Save the file hash result to <buf>. --
FileErrType FileGetHash(LargefileType* f, FileByteType* buf, FileSizeType bufLen) {

    FileSizeType infoSize = strlen((char*)f->hash) + 3;
    if(infoSize > bufLen) {
        log_error("The planned space size needs to be greater than %d, but the actual space size is %d", infoSize, bufLen);
        return FILE_ERR_BUF_TOO_SMALL;
    }
    memcpy(buf, "\"", 1);
    memcpy(buf+1, (char*)f->hash, 32);
    memcpy(buf+33, "\"", 2);
    log_debug("%s", buf);
    return FILE_SUCCESS;
}

// -- Free file object. --
FileVoidType FileInfoFree(LargefileType* f) {

    if (f->path != NULL){
        free(f->path);
    }
    if (f->name != NULL){
        free(f->name);
    }
    if (f->node != NULL){
        free(f->node);
    }
    if (f->user != NULL){
        free(f->user);
    }
    if (f->des != NULL){
        free(f->des);
    }
}
